[Rust] SAMをつかってLambda Authorizerのビルド&デプロイ
Introduction
ここのblogにもあるように、
(betaですが)cargo-lambdaをつかってSAMでRustコードのビルドが可能です。
本稿では、SAMを使用して、Rustで実装したAWS Lambdaをビルドおよびデプロイする
方法について解説します。
最初にSAMの基本について簡単に説明し、RustでLambdaを実装して
Lambda Authorizerも設定してみます。
SAM?
サーバーレスアーキテクチャはインフラの管理をシンプルにしながら、
スケーラブルなアプリを構築することを可能にします。
AWS Serverless Application Model(SAM)は、
AWSでサーバーレスアプリを簡単に開発するためのツールです。
SAMはAWS CloudFormationの拡張しており、YAML形式の設定ファイルを記述することで
アプリのインフラ設定やリソース定義が可能です。
Environments
- MacBook Pro (13-inch, M1, 2020)
- OS : MacOS 13.5.2
- Rust : 1.75.0
- aws-cli : 2.2.35
AWSアカウントはセットアップ済みとします。
Setup
まずはCargo Lambdaをインストールします。
% cargo install cargo-lambda
SAMはHomebrewでインストールできるのでbrew installを実行。
% brew tap aws/tap % brew install aws-sam-cli % sam --version SAM CLI, version 1.107.0
ビルドコマンド実行時にSAMはCargo Lambdaを使うようにするため、
↓のように環境変数を設定しておきましょう。
% export SAM_CLI_BETA_RUST_CARGO_LAMBDA=1
Try
ではSAMをつかってRust用テンプレートを作成します。
% sam init
AWS Quick StartからHello Worldを選択して、
rust (provided.al2)のrumtimeを指定しましょう。
samconfig.tomlファイルに下記パラメータを追加します。
[default.build.parameters] ・ ・ beta_features = true [default.sync.parameters] ・ ・ beta_features = true
そのままビルドとデプロイができる状態になってますので、
とりあえず確認してみましょう。
sam buildでビルドします。
% cd /path/your/example-project % sam build --beta-features
デプロイして動作確認してみましょう。
% sam deploy --guided
途中いくつか質問されますが、↓の認証に関する質問だけYesで回答して、
他はデフォルトでOK。
API Gatewayも作成されているので、発行されたURLにアクセスすれば
Lambdaが実行されてメッセージが表示されます。
※ dockerをいれてればsam invokeコマンド使ってローカルで動かすことも可能
HelloWorldFunction has no authentication. Is this okay? [y/N]: Y
必要なくなったらきれいにしておきましょう。
% sam delete
impliment Lambda Authorizer
Lambda Authorizerを使えばLambdaに簡単に認証機能を実装することができます。
今回はリクエストパラメータベースのLambda Authorizerを使って
先程のHelloWorldApi Lambdaに認証機能を追加してみましょう。
まずはtemplate.yamlにAuthorizerの設定を追加します。
Resources: AuthApi: Type: AWS::Serverless::Api Properties: StageName: Prod Auth: DefaultAuthorizer: ProxyAuthorizer Authorizers: ProxyAuthorizer: FunctionArn: !GetAtt AuthorizerFunction.Arn AuthorizerFunction: Type: AWS::Serverless::Function Metadata: BuildMethod: rust-cargolambda Properties: CodeUri: ./proxy_authorizer Handler: bootstrap Runtime: provided.al2023 Environment: Variables: RUST_BACKTRACE: "1" ・ ・
そして、template.yamlのHelloWorldFunction部分に RestApiIdで認証用Lambdaを関連付けます。
Events: HelloWorld: Type: Api Properties: Path: /hello Method: get RestApiId: !Ref AuthApi #←追加
次は認証用Lambdaを作成します。
プロジェクトディレクトリにauthorizerディレクトリを作成し、
src/main.rsを以下の内容で作成します。
//authorizer/src/main.rs use aws_lambda_events::event::apigw::{ ApiGatewayCustomAuthorizerPolicy, ApiGatewayCustomAuthorizerRequest, ApiGatewayCustomAuthorizerResponse, IamPolicyStatement, }; use lambda_runtime::{run, service_fn, Error, LambdaEvent}; use serde_json::json; use tokio; // API Gatewayのポリシーに関する定数 const POLICY_VERSION: &str = "2012-10-17"; const EXECUTE_API_ACTION: &str = "execute-api:Invoke"; const POLICY_EFFECT_ALLOW: &str = "Allow"; const PRINCIPAL_ID: &str = "user"; // 許可されたトークンの値を定義する定数 const AUTHORIZED_TOKEN: &str = "allow"; /// Lambda関数のエントリーポイント。 /// /// AWS Lambdaランタイムを起動し、カスタム認証ハンドラーを登録します。 #[tokio::main] async fn main() -> Result<(), Error> { run(service_fn(my_auth_handler)).await } /// カスタム認証handler /// /// API Gatewayからの認証リクエストを処理し、適切なIAMポリシーを返す。 /// /// # Arguments /// /// * `event` - API Gatewayからのカスタム認証リクエスト /// /// # Returns /// /// 認証が成功した場合は`ApiGatewayCustomAuthorizerResponse`を、失敗した場合は`Error`を返す async fn my_auth_handler( event: LambdaEvent<ApiGatewayCustomAuthorizerRequest>, ) -> Result<ApiGatewayCustomAuthorizerResponse, Error> { let token = event.payload.authorization_token; match token { Some(ref token_value) if token_value == AUTHORIZED_TOKEN => { let policy = create_allow_policy(event.payload.method_arn.unwrap()); Ok(create_authorizer_response(policy)) } Some(_) => Err(Error::from("Unauthorized")), None => Err(Error::from("Missing token")), } } /// 認証成功時に許可ポリシードキュメントを生成する。 /// /// # Arguments /// /// * `method_arn` - API GatewayのリソースARN /// /// # Returns /// /// `ApiGatewayCustomAuthorizerPolicy`のインスタンスを返す。 fn create_allow_policy(method_arn: String) -> ApiGatewayCustomAuthorizerPolicy { let stmt = IamPolicyStatement { action: vec![EXECUTE_API_ACTION.to_string()], resource: vec![method_arn], effect: Some(POLICY_EFFECT_ALLOW.to_owned()), }; ApiGatewayCustomAuthorizerPolicy { version: Some(POLICY_VERSION.to_string()), statement: vec![stmt], } } /// 認証成功時のレスポンスを生成する。 /// /// # Arguments /// /// * `policy` - 認証に成功したときに使用するIAMポリシー /// /// # Returns /// /// `ApiGatewayCustomAuthorizerResponse`のインスタンスを返する。 fn create_authorizer_response(policy: ApiGatewayCustomAuthorizerPolicy) -> ApiGatewayCustomAuthorizerResponse { let context = json!({ "simpleKey": "simpleValue" }); ApiGatewayCustomAuthorizerResponse { principal_id: Some(PRINCIPAL_ID.to_string()), policy_document: policy, context, usage_identifier_key: None, } }
Lambdaへのリクエスト時にAuthorizationヘッダにallowと指定していれば
実行を許可します。
Cargo.tomlは↓のようにします。
[package] name = "lambda-authorizer" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] lambda_runtime = "0.6.0" serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } aws-config = { version = "1.1.1", features = ["behavior-version-latest"] } serde_json = "1.0.108"
再度ビルド&デプロイで動作確認してみましょう。
ちなみにこの状態だと、デプロイ時に認証関連の確認は出ません。
% sam build --beta-features % sam deploy --guided
curlで適当なAuthorizationヘッダを指定すると、
認証用Lambdaによりrejectされます。
% curl -X GET <発行されたendpoint URL> -H "Authorization:hoo"/ {"message":"Unauthorized"}%
正しいヘッダを渡せば、HelloWorldApi Lambdaが実行されます。
% curl -X GET <発行されたendpoint URL> -H "Authorization:allow" {"message":"Hello World!"}%
Summary
今回はSAMを使って、RustでLambdaの実装をしてみました。
(まだbetaですが)ビルドもデプロイも簡単なのでぜひお試しください。